package com.inspur.edp.common.expr.parser;

import com.inspur.edp.common.expr.ast.ASTContext;
import com.inspur.edp.common.expr.ast.CompileUnitParser;
import com.inspur.edp.common.expr.exception.ExpressException;
import com.inspur.edp.common.type.Method;
import com.inspur.edp.common.type.Parameter;
import com.inspur.edp.common.type.Type;
import com.inspur.edp.common.type.TypeRefer;
import com.inspur.edp.common.type.parser.TypeParser;
import com.inspur.edp.common.type.cache.TypeThreadCache;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author lizhaorui
 * @date 2025/8/18
 * @description
 */
public class ExpressAstParser {

    private static ExpressAstParser instance;

    private Map<Class, ExpressVisitor> visitMap = new HashMap<>();

    public static ExpressAstParser getInstance() {
        if (instance == null) {
            synchronized (ExpressAstParser.class) {
                if (instance == null) {
                    ExpressAstParser temp = new ExpressAstParser();
                    temp.visitMap.put(CompileUnitParser.NumberValExprContext.class, new NumberValExprVisitor());
                    temp.visitMap.put(CompileUnitParser.StringValExprContext.class, new StringValExprVisitor());
                    temp.visitMap.put(CompileUnitParser.BooleanValExprContext.class, new BooleanValExprVisitor());
                    temp.visitMap.put(CompileUnitParser.ThisExprContext.class, new ThisExprVisitor());
                    temp.visitMap.put(CompileUnitParser.NullValExprContext.class, new NullValExprVisitor());
                    temp.visitMap.put(CompileUnitParser.NotExprContext.class, new NotExprVisitor());
                    temp.visitMap.put(CompileUnitParser.NewExprContext.class, new NewExprVisitor());
                    temp.visitMap.put(CompileUnitParser.VarDeclarationExprContext.class, new VarDeclarationExprVisitor());
                    temp.visitMap.put(CompileUnitParser.SimpleNameExprContext.class, new SimpleNameExprVisitor());
                    temp.visitMap.put(CompileUnitParser.PpmmPreExprContext.class, new PpmmPreExprVisitor());
                    temp.visitMap.put(CompileUnitParser.FieldAccessExprContext.class, new FieldAccessExprVisitor());
                    temp.visitMap.put(CompileUnitParser.PlusMinusPostExprContext.class, new PlusMinusPostExprVisitor());
                    temp.visitMap.put(CompileUnitParser.ArrayAccessExprContext.class, new ArrayAccessExprVisitor());
                    temp.visitMap.put(CompileUnitParser.MethodInvokeExprContext.class, new MethodInvokeExprVisitor());
                    temp.visitMap.put(CompileUnitParser.MultiplyDivideExprContext.class, new MultiplyDivideExprVisitor());
                    temp.visitMap.put(CompileUnitParser.PlusMinusExprContext.class, new PlusMinusExprVisitor());
                    temp.visitMap.put(CompileUnitParser.CompareExprContext.class, new CompareExprVisitor());
                    temp.visitMap.put(CompileUnitParser.TypeCastExprContext.class, new TypeCastExpressVisitor());
                    temp.visitMap.put(CompileUnitParser.AssignValExprContext.class, new AssignValExprVisitor());
                    temp.visitMap.put(CompileUnitParser.AndOrExprContext.class, new AndOrExprVisitor());
                    temp.visitMap.put(CompileUnitParser.AnonmousFuncExprContext.class, new AnonymousFuncExprVisitor());
                    temp.visitMap.put(CompileUnitParser.EnclosureExprContext.class, new EnclosureExprVisitor());
                    temp.visitMap.put(CompileUnitParser.ObjectValueExprContext.class, new ObjectValueExprVisitor());
                    temp.visitMap.put(CompileUnitParser.ArrayValueExprContext.class, new ArrayValueExprVisitor());
                    temp.visitMap.put(CompileUnitParser.SimplifyMethodInvokeExprContext.class, new SimplifyMethodInvokeExprVisitor());
                    temp.visitMap.put(CompileUnitParser.UnaryMinusExprContext.class, new UnaryMinusExprVisitor());
                    instance = temp;
                }
            }
        }
        return instance;
    }

    /**
     * 获取原始类型
     *
     * @param ctx
     * @param context
     * @return
     */
    public static TypeRefer getOriginalType(CompileUnitParser.ExpressContext ctx, ASTContext context) {
        if (ctx.originalType == null) {
            initType(ctx, context);
        }
        return ctx.originalType;
    }

    /**
     * 获取要求的类型
     *
     * @param ctx
     * @param context
     * @return
     */
    public static TypeRefer getRequireType(CompileUnitParser.ExpressContext ctx, ASTContext context) {
        if (ctx.originalType == null) {
            initType(ctx, context);
        }
        if (ctx.requireType == null) {
            return ctx.originalType;
        } else {
            return ctx.requireType;
        }
    }

    public static void initType(CompileUnitParser.ExpressContext ctx, ASTContext context) {
        if (ctx.typeInited) {
            return;
        }
        ExpressVisitor visitor = getVisitor(ctx);
        visitor.setType(ctx, context);
        ctx.typeInited = true;
    }

    public static List<TypeRefer> getParameterTypes(ASTContext context, CompileUnitParser.ParametersContext parametersContext) {
        List<TypeRefer> parameterTypes = null;
        if (parametersContext != null && parametersContext.parameter() != null) {
            parameterTypes = new ArrayList<>();
            for (CompileUnitParser.ParameterContext parameterContext : parametersContext.parameter()) {
                CompileUnitParser.ExpressContext expressContext = parameterContext.express();
                TypeRefer parameterType = ExpressAstParser.getOriginalType(expressContext, context);
                parameterTypes.add(parameterType);
            }
        }
        return parameterTypes;
    }


    public static ExpressVisitor getVisitor(CompileUnitParser.ExpressContext ctx) {
        ExpressAstParser manager = ExpressAstParser.getInstance();
        Class cls = ctx.getClass();
        if (manager.visitMap.containsKey(cls)) {
            return manager.visitMap.get(cls);
        } else {
            throw new ExpressException("not support primitive type " + cls.getName());
        }
    }

    public interface ExpressVisitor {
        void setType(CompileUnitParser.ExpressContext ctx, ASTContext context);
    }

    static class NumberValExprVisitor implements ExpressVisitor {

        @Override
        public void setType(CompileUnitParser.ExpressContext ctx, ASTContext context) {
            if (ctx.requireType != null) {
                ctx.originalType = ctx.requireType;
            } else {
                ctx.originalType = new TypeRefer("number");
            }
        }
    }

    static class StringValExprVisitor implements ExpressVisitor {

        @Override
        public void setType(CompileUnitParser.ExpressContext ctx, ASTContext context) {
            ctx.originalType = new TypeRefer("string");
        }
    }

    static class BooleanValExprVisitor implements ExpressVisitor {

        @Override
        public void setType(CompileUnitParser.ExpressContext ctx, ASTContext context) {
            ctx.originalType = new TypeRefer("boolean");
        }
    }

    static class ThisExprVisitor implements ExpressVisitor {

        @Override
        public void setType(CompileUnitParser.ExpressContext ctx, ASTContext context) {
            Type dslClass = context.getThisType();
            ctx.originalType = dslClass.buildTypeRefer();
        }
    }

    static class NullValExprVisitor implements ExpressVisitor {

        @Override
        public void setType(CompileUnitParser.ExpressContext ctx, ASTContext context) {
            ctx.originalType = new TypeRefer("any");
        }
    }

    static class NotExprVisitor implements ExpressVisitor {

        @Override
        public void setType(CompileUnitParser.ExpressContext ctx, ASTContext context) {
            ctx.originalType = new TypeRefer("boolean");
        }
    }

    static class NewExprVisitor implements ExpressVisitor {

        @Override
        public void setType(CompileUnitParser.ExpressContext ctx, ASTContext context) {
            CompileUnitParser.NewExprContext newInstContext = (CompileUnitParser.NewExprContext) ctx;
            ctx.originalType = TypeAstParser.getTypeRefer(newInstContext.type(), context);
        }
    }

    static class VarDeclarationExprVisitor implements ExpressVisitor {

        @Override
        public void setType(CompileUnitParser.ExpressContext ctx, ASTContext context) {
            CompileUnitParser.VarDeclarationExprContext varContext = (CompileUnitParser.VarDeclarationExprContext) ctx;
            ctx.originalType = TypeAstParser.getTypeRefer(varContext.type(), context);
            CompileUnitParser.ExpressContext valueExpr = ((CompileUnitParser.VarDeclarationExprContext) ctx).express();
            if (valueExpr != null) {
                valueExpr.requireType = ctx.originalType;
            }
        }
    }

    static class SimpleNameExprVisitor implements ExpressVisitor {

        @Override
        public void setType(CompileUnitParser.ExpressContext ctx, ASTContext context) {
            CompileUnitParser.SimpleNameExprContext simpleNameExprContext = (CompileUnitParser.SimpleNameExprContext) ctx;
            String referName = simpleNameExprContext.SimpleName().getText();
            if (context.containVarName(referName)) {
                ctx.originalType = context.getVarType(referName);
            } else if (context.containImportName(referName)) {
                String fullUrl = context.getImportFullName(referName);
                ctx.originalType = TypeThreadCache.getByUrl(fullUrl).buildTypeRefer();
            } else {
                Type dslType = context.getType(referName);
                ctx.originalType = dslType.buildTypeRefer();
            }
        }
    }

    static class PpmmPreExprVisitor implements ExpressVisitor {

        @Override
        public void setType(CompileUnitParser.ExpressContext ctx, ASTContext context) {
            CompileUnitParser.PpmmPreExprContext ppmmContext = (CompileUnitParser.PpmmPreExprContext) ctx;
            ctx.originalType = ExpressAstParser.getOriginalType(ppmmContext.express(), context);
        }
    }

    static class FieldAccessExprVisitor implements ExpressVisitor {

        @Override
        public void setType(CompileUnitParser.ExpressContext ctx, ASTContext context) {
            CompileUnitParser.FieldAccessExprContext fieldAccessContext = (CompileUnitParser.FieldAccessExprContext) ctx;
            TypeRefer objectType = ExpressAstParser.getOriginalType(fieldAccessContext.express(), context);
            String fieldName = fieldAccessContext.SimpleName().getText();
            ctx.originalType = TypeParser.getFieldType(objectType, fieldName);
        }
    }

    static class PlusMinusPostExprVisitor implements ExpressVisitor {

        @Override
        public void setType(CompileUnitParser.ExpressContext ctx, ASTContext context) {
            CompileUnitParser.PlusMinusExprContext pmContext = (CompileUnitParser.PlusMinusExprContext) ctx;
            ctx.originalType = ExpressAstParser.getOriginalType(pmContext.express(0), context);
        }
    }

    static class ArrayAccessExprVisitor implements ExpressVisitor {

        @Override
        public void setType(CompileUnitParser.ExpressContext ctx, ASTContext context) {
            CompileUnitParser.ArrayAccessExprContext arrayAccessExprContext = (CompileUnitParser.ArrayAccessExprContext) ctx;
            CompileUnitParser.ExpressContext expressContext = arrayAccessExprContext.express(0);
            TypeRefer arrayTypeRefer = ExpressAstParser.getOriginalType(expressContext, context);
            ctx.originalType = arrayTypeRefer.getGenericTypes().get(0);
        }
    }

    static class MethodInvokeExprVisitor implements ExpressVisitor {

        @Override
        public void setType(CompileUnitParser.ExpressContext ctx, ASTContext context) {
            CompileUnitParser.MethodInvokeExprContext methodInvokeExprContext = (CompileUnitParser.MethodInvokeExprContext) ctx;
            TypeRefer objectValueTypeRefer = ExpressAstParser.getOriginalType(methodInvokeExprContext.express(), context);
            String methodName = methodInvokeExprContext.SimpleName().getText();
            CompileUnitParser.ParametersContext parametersContext = methodInvokeExprContext.parameters();
            int parameterCount = 0;
            if (parametersContext != null && parametersContext.parameter() != null) {
                parameterCount = parametersContext.parameter().size();
            }
            ctx.originalType = TypeParser.getMethodRetType(objectValueTypeRefer, methodName, parameterCount);
            Method dslMethod = TypeParser.getMethod(objectValueTypeRefer, methodName, parameterCount);
            if (parametersContext != null && parametersContext.parameter() != null) {
                List<Parameter> parameters = dslMethod.getParameters();
                int totalCount = parameters.size();
                Parameter lastParameter = parameters.get(totalCount - 1);
                boolean hasVarArgs = lastParameter.isVarArgs();
                if (!hasVarArgs) {
                    int index = 0;
                    for (CompileUnitParser.ParameterContext parameterContext : parametersContext.parameter()) {
                        parameterContext.express().requireType = parameters.get(index).getType();
                        index++;
                    }
                } else {
                    int index = 0;
                    for (CompileUnitParser.ParameterContext parameterContext : parametersContext.parameter()) {
                        if (index >= totalCount - 1) {
                            parameterContext.express().requireType = parameters.get(totalCount - 1).getType();
                        } else {
                            parameterContext.express().requireType = parameters.get(index).getType();
                        }
                        index++;
                    }
                }

            }
        }
    }

    static class MultiplyDivideExprVisitor implements ExpressVisitor {

        @Override
        public void setType(CompileUnitParser.ExpressContext ctx, ASTContext context) {
            CompileUnitParser.MultiplyDivideExprContext realContext = (CompileUnitParser.MultiplyDivideExprContext) ctx;
            CompileUnitParser.ExpressContext leftExpr = realContext.express(0);
            CompileUnitParser.ExpressContext rightExpr = realContext.express(1);
            TypeRefer leftTypeRefer = ExpressAstParser.getOriginalType(leftExpr, context);
            TypeRefer rightTypeRefer = ExpressAstParser.getOriginalType(rightExpr, context);
            if (leftTypeRefer.isBigDecimal() || rightTypeRefer.isBigDecimal()) {
                ctx.originalType = TypeRefer.BIGDECIMAL_TYPE;
                leftExpr.requireType = TypeRefer.BIGDECIMAL_TYPE;
                rightExpr.requireType = TypeRefer.BIGDECIMAL_TYPE;
            } else {
                ctx.originalType = TypeRefer.NUMBER_TYPE;
            }
        }
    }

    static class PlusMinusExprVisitor implements ExpressVisitor {
        @Override
        public void setType(CompileUnitParser.ExpressContext ctx, ASTContext context) {
            CompileUnitParser.PlusMinusExprContext pmContext = (CompileUnitParser.PlusMinusExprContext) ctx;
            CompileUnitParser.ExpressContext leftExpr = pmContext.express(0);
            CompileUnitParser.ExpressContext rightExpr = pmContext.express(1);
            TypeRefer leftTypeRefer = ExpressAstParser.getOriginalType(leftExpr, context);
            TypeRefer rightTypeRefer = ExpressAstParser.getOriginalType(rightExpr, context);
            if (pmContext.Plus() != null) {
                if (leftTypeRefer.isString() || rightTypeRefer.isString()) {
                    ctx.originalType = TypeRefer.STRING_TYPE;
                    leftExpr.requireType = TypeRefer.STRING_TYPE;
                    rightExpr.requireType = TypeRefer.STRING_TYPE;
                } else if (leftTypeRefer.isBigDecimal() || rightTypeRefer.isBigDecimal()) {
                    ctx.originalType = TypeRefer.BIGDECIMAL_TYPE;
                    leftExpr.requireType = TypeRefer.BIGDECIMAL_TYPE;
                    rightExpr.requireType = TypeRefer.BIGDECIMAL_TYPE;
                } else {
                    ctx.originalType = TypeRefer.NUMBER_TYPE;
                }
                if (!((leftTypeRefer.isString() || leftTypeRefer.isNumber()) && (rightTypeRefer.isString() || rightTypeRefer.isNumber()))) {
                    throw new ExpressException(String.format("type %s and type %s not apply plus operate.", leftTypeRefer.getTypeId(), rightTypeRefer.getTypeId()));
                }
            } else {
                if (leftTypeRefer.isBigDecimal() || rightTypeRefer.isBigDecimal()) {
                    ctx.originalType = TypeRefer.BIGDECIMAL_TYPE;
                    leftExpr.requireType = TypeRefer.BIGDECIMAL_TYPE;
                    rightExpr.requireType = TypeRefer.BIGDECIMAL_TYPE;
                } else {
                    ctx.originalType = TypeRefer.NUMBER_TYPE;
                }
                if (!(leftTypeRefer.isNumber() && rightTypeRefer.isNumber())) {
                    throw new ExpressException(String.format("type %s and type %s not apply plus operate.", leftTypeRefer.getTypeId(), rightTypeRefer.getTypeId()));
                }
            }
        }
    }

    static class CompareExprVisitor implements ExpressVisitor {

        @Override
        public void setType(CompileUnitParser.ExpressContext ctx, ASTContext context) {
            ctx.originalType = new TypeRefer("boolean");
            CompileUnitParser.CompareExprContext compareExprContext = (CompileUnitParser.CompareExprContext) ctx;
            CompileUnitParser.ExpressContext leftExpr = compareExprContext.express(0);
            CompileUnitParser.ExpressContext rightExpr = compareExprContext.express(1);
            TypeRefer leftTypeRefer = ExpressAstParser.getOriginalType(leftExpr, context);
            TypeRefer rightTypeRefer = ExpressAstParser.getOriginalType(rightExpr, context);
            if (leftTypeRefer.isBigDecimal() || rightTypeRefer.isBigDecimal()) {
                leftExpr.requireType = TypeRefer.BIGDECIMAL_TYPE;
                rightExpr.requireType = TypeRefer.BIGDECIMAL_TYPE;
            }
        }
    }

    static class TypeCastExpressVisitor implements ExpressVisitor {

        @Override
        public void setType(CompileUnitParser.ExpressContext ctx, ASTContext context) {
            CompileUnitParser.TypeCastExprContext castExprContext = (CompileUnitParser.TypeCastExprContext) ctx;
            ctx.originalType = TypeAstParser.getTypeRefer(castExprContext.type(), context);
        }
    }

    static class AssignValExprVisitor implements ExpressVisitor {

        @Override
        public void setType(CompileUnitParser.ExpressContext ctx, ASTContext context) {
            CompileUnitParser.AssignValExprContext assignValExprContext = (CompileUnitParser.AssignValExprContext) ctx;
            CompileUnitParser.ExpressContext leftExp = assignValExprContext.express(0);
            CompileUnitParser.ExpressContext rightExpr = assignValExprContext.express(1);
            rightExpr.requireType = getRequireType(leftExp, context);
        }
    }

    static class AndOrExprVisitor implements ExpressVisitor {

        @Override
        public void setType(CompileUnitParser.ExpressContext ctx, ASTContext context) {
            ctx.originalType = new TypeRefer("boolean");
        }
    }

    static class AnonymousFuncExprVisitor implements ExpressVisitor {

        @Override
        public void setType(CompileUnitParser.ExpressContext ctx, ASTContext context) {

        }
    }


    static class EnclosureExprVisitor implements ExpressVisitor {

        @Override
        public void setType(CompileUnitParser.ExpressContext ctx, ASTContext context) {
            CompileUnitParser.EnclosureExprContext enclosureExprContext = (CompileUnitParser.EnclosureExprContext) ctx;
            ctx.originalType = ExpressAstParser.getOriginalType(enclosureExprContext.express(), context);
        }
    }

    static class ObjectValueExprVisitor implements ExpressVisitor {

        @Override
        public void setType(CompileUnitParser.ExpressContext ctx, ASTContext context) {

        }
    }

    static class ArrayValueExprVisitor implements ExpressVisitor {

        @Override
        public void setType(CompileUnitParser.ExpressContext ctx, ASTContext context) {

        }
    }


    static class SimplifyMethodInvokeExprVisitor implements ExpressVisitor {

        @Override
        public void setType(CompileUnitParser.ExpressContext ctx, ASTContext context) {
            CompileUnitParser.SimplifyMethodInvokeExprContext express = (CompileUnitParser.SimplifyMethodInvokeExprContext) ctx;
            String methodName = express.SimpleName().getText();
            List<TypeRefer> parameterTypes = ExpressAstParser.getParameterTypes(context, express.parameters());
            TypeRefer thisTypeRefer = context.getThisType().buildTypeRefer();
            ctx.originalType = TypeParser.getMethodRetType(thisTypeRefer, methodName, parameterTypes);
        }
    }

    static class UnaryMinusExprVisitor implements ExpressVisitor {

        @Override
        public void setType(CompileUnitParser.ExpressContext ctx, ASTContext context) {
            ctx.originalType = new TypeRefer("number");
        }
    }

}
